//------------------------------------------------------------------------------
// X2D Game Engine
// Copyright (C) 2006, 2007, 2008 Janusz Kowalski
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//------------------------------------------------------------------------------

#include <boost/filesystem.hpp>

namespace fs = boost::filesystem;

#include "SDL.h"
#include "SDL_mixer.h"
#include <png.h>
#include <exception>
#ifdef OS_UNIX
#include <unistd.h>
#endif

#include "x2tk.h"
#include "x2d_ext.h"
#include "x2d_main.h"
#include "x2d_gameMode.h"
#include "x2d_editMode.h"
#include "x2tk_Screen.h"
#include "x2tk_VPanel.h"
#include "x2tk_Button.h"
#include "x2tk_TextField.h"
#include "x2d_MainScreen.h"
#include "x2tk_Window.h"
#include "x2tk_VText.h"
#include "imgSDL.h"
#include "mainLoop.h"
#include "global.h"

#include "pmsg.h"

#ifdef X2D_WITH_OPENGL
#ifdef OS_WINDOWS
#include <windows.h>
#endif
#include "GL/gl.h"
#include "GL/glu.h"
#endif

#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"

namespace x2d {

namespace conf {
  // Frames Per Second limit.
  int FPSLimit = 60;

  #ifdef WITH_SOUND
  // In-game sounds.
  bool SoundEnabled = true;
  #endif

  // Default screen dimensions. These are only configuration options, and
  // sometimes may be different than the actual ones.
  int ScreenWidth = 640;
  int ScreenHeight = 480;
}

bool flagMainMenu=false;
bool flagGameMode=false;
bool flagEditMode=false;

bool isMainMenu() {return flagMainMenu;}
bool isGameMode() {return flagGameMode;}
bool isEditMode() {return flagEditMode;}

int framesPerSecond_ = 0;

int getFPS() {
  return framesPerSecond_;
}

void startGame() {
  // Exit current mode.
  if(isGameMode()) {
    gameModeEnd(); flagGameMode=false;
  } else if(isEditMode()) {
    editModeEnd(); flagEditMode=false;
  }
  
  // Set new mode.
  gameModeStart(); flagGameMode=true;
}

void startEdit() {
  // Exit curent mode.
  if(isGameMode()) {
    gameModeEnd(); flagGameMode=false;
  } else if(isEditMode()) {
    editModeEnd(); flagEditMode=false;
  }
  
  // Set new mode.
  editModeStart(); flagEditMode=true;
}

static const char *main_msg[]={
  "== " NAME_FULL " ==",
  "",
  "= PROGRAMMING =",
  "JANUSZ KOWALSKI",
  "",
  "= STORY, GRAPHICS & LEVEL DESIGN =",
  "JANUSZ KOWALSKI",
  "AND",
  "ARNOLD MIODUSZEWSKI",
  0
};

class DialogAbout : public x2tk::Widget {
  private:
    SDL_Surface *tex;
    Font font_;
  public:
    DialogAbout() : x2tk::Widget(0,0,600,240) {
      int maxwidth=0;
      int i;
      for(i=0;main_msg[i];i++) {
        int n;
        for(n=0; main_msg[i][n]; n++);
        if(16*n>maxwidth) maxwidth=16*n;
      }
      resize(maxwidth+32, (16+4)*i+32);
      tex=loadImage(X2D_PATH_DATA "circles/texD.png", true, true);
      font_ = Font(X2D_PATH_FONT "HiTechFont_Black.png");
    }
    
    virtual void layout() {
      if(getParent()!=0) {
        int w = getParent()->getW(), h = getParent()->getH();
        // Center.
        setX((w-getW())/2); setY((h-getH())/2);
      }
    }
    
    ~DialogAbout() {
      SDL_FreeSurface(tex);
    }
    
    virtual void draw(GraphicContext &g) {
      x2tk::Widget::draw(g);
      g.textureFill(tex);
      for(int i=0; main_msg[i]; i++) {
        int x=(getW()-font_.getW(main_msg[i]))/2;
        int y=(16+4)*i+16;
        font_.drawText(g, x, y, main_msg[i]);
      }
    }
    
    bool mouseDown(int type) {
      Widget::mouseDown(type);
      callLater(this,0);
      return true;
    }
    
    virtual void timed(int id) {
      delete this;
    };
};

class LineTest : public x2tk::Widget {
  public:
  LineTest() : x2tk::Widget(1,1,100,100) {
  }
  virtual void draw(GraphicContext &g) {
    g.setColor(255,255,255,255);
    for(int i=0; i<4096*2; i++) {
      g.line(rand()%getW(), rand()%getH(), rand()%getW(), rand()%getH());
    }
  }
};

class InfoFPS : public x2tk::Widget {
  private:
    Font font_;
    char buffer_[16];
  public:
    InfoFPS() : x2tk::Widget(0,0,200,32), font_(X2D_PATH_FONT "HiTechFont_White.png") { };
    virtual void draw(GraphicContext &g) {
#ifdef MSVC
      _snprintf_s(buffer_, 16, 16, "FPS: %d", getFPS());
#else
      snprintf(buffer_, 16, "FPS: %d", getFPS());
#endif
      font_.drawText(g,0,0,buffer_);
    }
    
    virtual void layout() {
      if(getParent()!=0) {
        setX(0); setY(getParent()->getH()-32);
      }
    }
};

class DialogMain : public x2tk::VPanel {
  private:
    enum {ACTION_Nothing, ACTION_NewGame, ACTION_Continue, ACTION_StartEdit, ACTION_About, ACTION_Quit};
  public:
    DialogMain() : x2tk::VPanel(150, 100, 200, 200) {
      put(new x2tk::Button("NEW GAME",ACTION_NewGame));
      put(new x2tk::Button("CONTINUE",ACTION_Continue));
      put(new x2tk::Button("EDITOR", ACTION_StartEdit));
      put(new x2tk::Button("ABOUT", ACTION_About));
      put(new x2tk::Button("QUIT",ACTION_Quit));
      put(new x2tk::TextField(1,1,4,4));
    }
    
    virtual void layout() {
      if(getParent()!=0) {
        int w = getParent()->getW(), h = getParent()->getH();
        resize(w/2,h/2);
        setX((w-getW())/2); setY((h-getH())/2);
      }
    }
    
    virtual void action(int id, Widget *origin) {
      switch(id) {
        case ACTION_NewGame:
          startGame();
          toggleMainMenu();
          break;
        case ACTION_Continue:
#ifdef DEBUG
          x2tk::Widget *w1;
          x2tk::Widget *w2;
          w1=new x2tk::Button("H");
          //w1 = new LineTest();
          //w1=new x2tk::VText(1,1,10,10);
          //w1->load("README.txt");
          w2=new x2tk::Window("Simple Window", 100, 100, 400, 400);
          ((x2tk::Window*)w2)->setContent(w1);
          getScreenWidget()->add(w2);
#endif
          break;
        case ACTION_StartEdit:
          startEdit();
          toggleMainMenu();
          break;
        case ACTION_About:
          // !! Care should be taken.
          getScreenWidget()->add(new DialogAbout());
          break;
        case ACTION_Quit:
          mainExit();
          break;
        default:
          pmsgWarningF("%s", "DialogMain : Unknown action.");
          break;
      }
    }
};

static x2tk::Screen *x2tkScreen;

x2tk::Screen *getScreenWidget() {
  return x2tkScreen;
}

static Uint32 sdlVideoFlags = SDL_DOUBLEBUF | SDL_HWSURFACE | SDL_RESIZABLE;

// One function to set screen mode.
void setVideo(int width, int height, Uint32 flags) {

  pmsgDebugF("Setting video mode %dx%d\n", int(width), int(height));
  if (!SDL_SetVideoMode (width, height, 16, flags)) {
    pmsgErrorF("SDL_SetVideoMode(...) failed.");
    pmsgErrorF(" Reason: %s\n", SDL_GetError());
    exit(EXIT_FAILURE);
  }

#ifdef X2D_WITH_OPENGL
  glClearColor(0,0,0,0);
  glEnable(GL_TEXTURE_2D);
  // User replace mode for textures.
  glEnable(GL_BLEND);
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glViewport(0,0,width,height);
  glOrtho(0,width,height,0,-1.0,1.0);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
#endif
}

void checkEvent() {
  SDL_Event event;
  while (SDL_PollEvent (&event)) {
    // Check some important SDL events.
    switch (event.type) {
      case SDL_QUIT:
        mainExit();
        break;
      case SDL_VIDEORESIZE:
        // Just resize, using the current flags.
        setVideo(event.resize.w, event.resize.h, SDL_GetVideoSurface()->flags);
        break;
      // Emergency exit - CTRL-Q.
      case SDL_KEYDOWN:
        if(event.key.keysym.sym==SDLK_ESCAPE) toggleMainMenu();
        else if(event.key.keysym.sym==SDLK_q && (event.key.keysym.mod&KMOD_LCTRL)!=0) {
          mainExit();
        }
    }
    
    x2tkScreen->handleSDLEvent(event);
  }
}

// Event checking.
TimeT checkEv_cb(Uint32 currentTime, void *data) {
  checkEvent();
  return 10;  // Do not check events too often.
}

//template <class T> T max(T a, T b) { return a>b?a:b; }

static Uint32 nextDrawTime=0;

TimeT tick_cb(Uint32 currentTime, void *data) {
  static int nframes = 0;
  static int lastTime = 0;

  if(currentTime >= nextDrawTime) {
    getScreenWidget()->draw();
    nframes++;
    nextDrawTime = nextDrawTime + 1000/conf::FPSLimit;
  }

  // Lose frames.
  nextDrawTime=max(nextDrawTime, currentTime);

  // Do x2tk calls.
  x2tk::doCalls();

  if(SDL_GetTicks() - lastTime >= 500) {
    framesPerSecond_ = nframes * 1000 / (SDL_GetTicks() - lastTime);
    lastTime = SDL_GetTicks();
    nframes=0;
  }
  
  return nextDrawTime-currentTime;
}

void myTerminate() {
  SDL_Quit();
  abort();
}

static void usage() {
  printf("COMMAND LINE OPTIONS:\n");
  printf("  --fullscreen\n");
  printf("  --windowed\n");
  printf("  --nosound\n");
  printf("  --help\n");
}

// Print error message from the stack.
void luaReport(lua_State *L) {
  if(!lua_isnil(L,-1)) {
    const char *msg = lua_tostring(L, -1);
    if(msg==NULL) pmsgErrorF(" (error object is not a string)\n");
    else pmsgErrorF(" %s\n", msg);
    lua_pop(L,1);
  }
}

#define LUA_GET_GLOBAL(state, option) luaGetGlobal(state, #option, option)

void luaGetGlobal(lua_State *L, const char *name, int &value) {
  lua_getglobal(L, name);
  if(!lua_isnil(L,-1)) {
    if(!lua_isnumber(L,-1)) {
      pmsgErrorF(" In config file: %s has to be a number.\n", name);
      exit(1);
    } else {
      value=static_cast<int>(lua_tointeger(L, -1));
    }
  }
  lua_pop(L, 1);
}

void luaGetGlobal(lua_State *L, const char *name, bool &value) {
  lua_getglobal(L, name);
  if(!lua_isnil(L,-1)) {
    if(!lua_isboolean(L,-1)) {
      pmsgErrorF(" In config file: %s has to be boolean.\n", name);
      exit(1);
    } else {
      value=(lua_toboolean(L, -1)!=0);
    }
  }
  lua_pop(L, 1);
}

void mainInit(int argc, char *argv[]) {
  pmsgInfoF("%s", "x2d::mainInit()\n");

// Check some macros (defined by cmake(?)).
#if defined(OS_WINDOWS)
  pmsgInfoF("OS: %s\n", "WINDOWS");
#elif defined(OS_UNIX)
  pmsgInfoF("OS: %s\n", "UNIX-like");
#else
  pmsgInfoF("OS: %s\n", "Unknown");
#endif

  pmsgInfoF("Compiled with libpng %s; using libpng %s.\n", PNG_LIBPNG_VER_STRING, png_libpng_ver);
  pmsgInfoF("Compiled with zlib %s; using zlib %s\n", ZLIB_VERSION, zlib_version);

  // Initialize Lua scripting.
  lua_State *luaState = lua_open();
  if(!luaState) {
    pmsgErrorF("Lua initialization failed.\n");
    exit(EXIT_FAILURE);
  }
  
  // Open configuration file.
  // !! Check if this file exists.
  if(fs::exists("/tmp/config.lua")) {
    if(!!luaL_dofile(luaState,"/tmp/config.lua")) {
      pmsgErrorF("Error in configuration file.");
      luaReport(luaState);
      exit(EXIT_FAILURE);
    }

    // Get configuration options from the script.
    {
      using namespace conf;
      LUA_GET_GLOBAL(luaState, FPSLimit);
      LUA_GET_GLOBAL(luaState, SoundEnabled);
      LUA_GET_GLOBAL(luaState, ScreenWidth);
      LUA_GET_GLOBAL(luaState, ScreenHeight);
    }
  } else {
    pmsgInfoF("No configuration file found.\n");
  }

  // Check command line arguments.
  for(int i=1; i<argc; i++) {
    if(0) {
    }
    else if(strcmp("--fullscreen", argv[i])==0) {
      sdlVideoFlags |= SDL_FULLSCREEN;
    }
    else if(strcmp("--windowed", argv[i])==0) {
      sdlVideoFlags &= ~SDL_FULLSCREEN;
    }
#ifdef WITH_SOUND
    else if(strcmp("--nosound", argv[i])==0) {
      conf::SoundEnabled = false;
    }
#endif
    else if(strcmp("--help", argv[i])==0) {
      usage();
      return;
    }
    else {
      printf("Check your comand line options!");
      usage();
      return;
    }
  }
  
#ifdef X2D_WITH_OPENGL
  // OpenGL
  sdlVideoFlags |= SDL_OPENGL;
#endif

  // Initialize SDL
  if (SDL_Init (SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0)
  {
    pmsgErrorF("Couldn't initialize SDL: %s\n", SDL_GetError());
    return;
  }
  // !! Should we use exit() at all?
  atexit (SDL_Quit);
  
  // Set custom terminate function.
  std::set_terminate(myTerminate);
  
  pmsgInfoF("%s", "SDL_Init done\n");

  // Set custom icon in the window manager.
  const char *ICON_PATH = X2D_PATH_DATA "icon.png";
  try {
    SDL_Surface *icon=loadImage(ICON_PATH, true, false);
    SDL_WM_SetIcon(icon, 0);
  } catch (...) {
    pmsgWarningF("Couldn't open icon file (%s)\n", ICON_PATH);
  }
  // SDL_WM_SetIcon seems supporting colorkeys only for 1,2,4 bpp surfaces (SDL_SRCCOLORKEY), and additonaly
  // supporting per-pixel alpha values for 2,4 bpp surfaces (SDL_SRCALPHA).

  // Set caption
  SDL_WM_SetCaption(NAME_FULL, NAME_FULL);

  // Set video mode.
  setVideo(conf::ScreenWidth, conf::ScreenHeight, sdlVideoFlags);
  
  // Set Audio
#ifdef WITH_SOUND
  if(conf::SoundEnabled) {
    if(Mix_OpenAudio(22050, AUDIO_S16, 2, 1024)) {
      pmsgWarningF("%s", "Unable to open audio!\n");
    } else {
      Mix_Music *music=Mix_LoadMUS(X2D_PATH_MUSIC "answers.ogg");
      if(!music) {
        pmsgWarningF("Mix_LoadMUS(...): %s\n", Mix_GetError());
      } else {
        Mix_PlayMusic(music,0);
      }
    }
  }
#endif

  x2d::logHardwareInfo();
  
  // Init x2tk.
  x2tk::init();
    
  // Create screen widget
  x2d::MainScreen *widgetScreen=new x2d::MainScreen(SDL_GetVideoSurface());
  x2tkScreen=widgetScreen;
  
  widgetScreen->add(new InfoFPS());
  
  // Show main menu
  x2d::toggleMainMenu();
  
  mainLoop_addFn(checkEv_cb, 0);
  mainLoop_addFn(tick_cb, 0);
  mainLoop_run();
  
  // ...
  x2tk::done();
  lua_close(luaState);
}

void mainExit() {
  mainLoop_stop();
}

void toggleMainMenu() {
  static DialogMain *dialogMain=0;
  
  if(!dialogMain) {
    dialogMain = new DialogMain();
    x2tkScreen->add(dialogMain);
    dialogMain->setVisible(false);
  }
  
  if(!flagMainMenu) {
    flagMainMenu=true;
    dialogMain->setVisible(true);
    dialogMain->toTheTop();
    dialogMain->setFocus();
  } else {
    flagMainMenu=false;
    dialogMain->setVisible(false);
  }
}

} // namespace x2d
